#include <graphics/GLBufferObject.h>

namespace ne
{
    GLBufferObject::GLBufferObject(const BufferType bt, const BufferUsage usage, const uint32_t length)
        : GLObject(),
          mType(bt),
          mUsage(usage),
          mSize(length),
          mIsLocked(false)
    {
        glGenBuffers(1, &mSource);
        glBindBuffer(mType, mSource);
        glBufferData(mType, mSize, 0, mUsage);
    }

    BufferType GLBufferObject::getType() const
    {
        return mType;
    }

    BufferUsage GLBufferObject::getUsage() const
    {
        return mUsage;
    }

    uint32_t GLBufferObject::getSize() const
    {
        return mSize;
    }

    bool GLBufferObject::isLocked() const
    {
        return mIsLocked;
    }

    void* GLBufferObject::lock(const BufferAccess access, const uint32_t offset, const uint32_t length, const bool discardWholeBuffer)
    {
        if (mIsLocked || offset + length > mSize)
        {
            return 0;
        }

        glBindBuffer(mType, mSource);

        if (discardWholeBuffer)
        {
            glBufferData(mType, mSize, 0, mUsage);
        }

#if NE_RENDER_SYSTEM == NE_RENDER_SYSTEM_OPENGL
        void *pBuffer = glMapBuffer(mType, access);
#elif NE_RENDER_SYSTEM == NE_RENDER_SYSTEM_OPENGLES
        void *pBuffer = glMapBufferOES(mType, access);
#endif

        mIsLocked = true;
        return static_cast<void *>(static_cast<byte*>(pBuffer) + offset);
    }

    bool GLBufferObject::unlock()
    {
        if (!mIsLocked)
        {
            return false;
        }

        glBindBuffer(mType, mSource);

#if NE_RENDER_SYSTEM == NE_RENDER_SYSTEM_OPENGL
        GLboolean res = glUnmapBuffer(mType);
#elif NE_RENDER_SYSTEM == NE_RENDER_SYSTEM_OPENGLES
        GLboolean res = glUnmapBufferOES(mType);
#endif
        if (res)
        {
            mIsLocked = false;
            return true;
        }

        return false;
    }

    bool GLBufferObject::readData(const uint32_t offset, const uint32_t length, void *pDest)
    {
        if (offset + length > mSize)
        {
            return false;
        }

        glBindBuffer(mType, mSource);

#if NE_RENDER_SYSTEM == NE_RENDER_SYSTEM_OPENGL
        glGetBufferSubData(mType, offset, length, pDest);
#elif NE_RENDER_SYSTEM == NE_RENDER_SYSTEM_OPENGLES
        void *pBuffer = lock(BOA_READ_ONLY, offset, length);
        if (!pBuffer)
        {
            return false;
        }
        memcpy(pDest, pBuffer, length);
        unlock();
#endif

        return true;
    }

    bool GLBufferObject::writeData(const uint32_t offset, const uint32_t length, const void *pSource, const bool discardWholeBuffer)
    {
        if (offset + length > mSize)
        {
            return false;
        }

        glBindBuffer(mType, mSource);
        if (offset == 0 && length == mSize)
        {
            glBufferData(mType, mSize, pSource, mUsage);
        }
        else
        {
            if (discardWholeBuffer)
            {
                glBufferData(mType, mSize, 0, mUsage);
            }
            glBufferSubData(mType, offset, length, pSource);
        }

        return true;
    }

    bool GLBufferObject::copyDataFrom(GLBufferObject &srcVBO, const uint32_t srcOffset, const uint32_t destOffset, const uint32_t length, const bool discardWholeBuffer)
    {
        if (srcOffset + length > srcVBO.getSize() || destOffset + length > mSize)
        {
            return false;
        }

        const void *pSource = srcVBO.lock(BOA_READ_ONLY, srcOffset, length);

        if (!pSource)
        {
            return false;
        }

        if (!writeData(destOffset, length, pSource, discardWholeBuffer))
        {
            return false;
        }
        return true;
    }

    bool GLBufferObject::copyDataFrom(GLBufferObject &srcVBO)
    {
        uint32_t length = std::min(mSize, srcVBO.getSize());
        return copyDataFrom(srcVBO, 0, 0, length, true);
    }

    GLBufferObject::~GLBufferObject()
    {
        glDeleteBuffers(1, &mSource);
    }
};
